#include <stx/imp/cmn.hpp>

inline namespace stx
{
std::ostream& operator<<(std::ostream& os, Tco const tco)
{
    if (tco == Tco::K_RESET)
    {
        os << "\033[m\e[0m"sv;
    }
    else if (tco == Tco::K_BOLD)
    {
        os << "\e[1m"sv;
    }
    else if (tco == Tco::K_ERASE_CUR_LINE)
    {
        os << "\33[2K\r"sv;
    }
    else
    {
        auto const b1 = ton(tco) >= 30;
        auto const b2 = ton(tco) <= 37;
        auto const b3 = ton(tco) >= 40;
        auto const b4 = ton(tco) <= 47;

        auto const is_fg_color = b1 && b2;
        auto const is_bg_color = b3 && b4;

        if (is_fg_color || is_bg_color)
        {
            os << "\033["sv << static_cast<std::uint32_t>(tco) << "m"sv;
        }
    }

    return os;
}

fs::path operator""_fp(char const* const sz, std::size_t const size)
{
    return fs::path(sz, sz + size);
}

/*
The default value fs.nr_open is 1024 * 1024 = 1048576.

The maximum value of fs.nr_open is limited to fs.file-max in kernel, which is
5255573 on x86_64.

fs.file-nr output 3 values: 

    n1 n2 n3

n1: The number of file handles the Linux kernel has allocated.
n2: The number of file handles the Linux kernel has allocated but unused.
n3: Always equal to fs.file-max.

The value of "Max open files" (ulimit -n), which is defaulted to 1024, is
limited to fs.nr_open value. This is the maximum number of open files in one
process. This can be reset by the process itself.

RLIMIT_NOFILE (rlim_cur: 1024) <= fs.nr_open (rlim_max: 1048576) 
<= fs.file-max (5255573)

::setrlimit() needs CAP_SYS_RESOURCE, so, we must start our server as follows:

    sudo setcap CAP_SYS_RESOURCE=+pie ./my.exe
    ./my.exe

or create a custom gdb script as follows:

    #!/bin/bash
	sudo /usr/bin/gdb $*

then modify vscode's launch.json: 

    "miDebuggerPath": "/data/bin/sudogdb"

Debugging in sudo mode in vscode
--------------------------------
- In wsl, add your username to a sudo config file.

- Replace MY_USERNAME below to your username name.

    sudo vim /etc/sudoers.d/MY_USERNAME

- Add the following line:

    MY_USERNAME ALL=(ALL) NOPASSWD:ALL

- If you don't replace MY_USERNAME with the target username you will receive an
  error no valid sudoers sources found.

Modify fs.nr_open & fs.file-max
-------------------------------
1. sudo vim /etc/sysctl.conf
2. Append the two lines to /etc/sysctl.conf:

     fs.file-max=5000000
     fs.nr_open=5000000

3. Reload the config from /etc/sysctl.conf and take effect immediately.

     sudo sysctl -p

4. Now, /proc/sys/fs/file-max and /proc/sys/fs/nr_open will have the modified 
   values.

5. For convenience, add the lines to /etc/profile

     sudo sysctl -p
	 sudo bash -c "ulimit -n 5000000"

6. Add the following lines to the file /etc/security/limits.conf

     * soft core   unlimited
     * soft nofile 5000000
     * hard nofile 5000000
     # End of file

7. After login, execute:

     su username
   
   or execute:
	
	 su root

   then, `ulimit -a` will take effect.
*/
void initPosixEnv()
{
    std::signal(SIGPIPE, SIG_IGN);

    auto const s_max_user_watches =
        readFileToString("/proc/sys/fs/epoll/max_user_watches"_fp).get();
    if (s_max_user_watches.empty())
    {
        RUNTIME_ERROR("Failed to open /proc/sys/fs/epoll/max_user_watches"sv);
    }

    auto opt = ton(s_max_user_watches);
    if (!opt)
    {
        RUNTIME_ERROR("Invalid /proc/sys/fs/epoll/max_user_watches: "sv,
                      s_max_user_watches);
    }

    if (*opt < 1_Mi)
    {
        RUNTIME_ERROR("/proc/sys/fs/epoll/max_user_watches is too small: "sv,
                      s_max_user_watches);
    }

    auto const s_nr_open = readFileToString("/proc/sys/fs/nr_open"_fp).get();
    if (s_nr_open.empty())
    {
        RUNTIME_ERROR("Failed to open /proc/sys/fs/nr_open"sv);
    }

    opt = ton(s_nr_open);
    if (!opt)
    {
        RUNTIME_ERROR("Invalid /proc/sys/fs/nr_open: "sv, s_nr_open);
    }

    auto v = ::rlimit{};
    if (::getrlimit(RLIMIT_NOFILE, &v))
    {
        RUNTIME_ERROR("Failed to ::getrlimit(RLIMIT_NOFILE): "sv, M_SYS_ERR);
    }

    if (v.rlim_cur < *opt)
    {
        v.rlim_cur = *opt;
        v.rlim_max = *opt;

        if (::setrlimit(RLIMIT_NOFILE, &v))
        {
            RUNTIME_ERROR("Failed to ::setrlimit(RLIMIT_NOFILE): "sv,
                          M_SYS_ERR);
        }
    }

    if (::getrlimit(RLIMIT_NOFILE, &v))
    {
        RUNTIME_ERROR("Failed to ::getrlimit(RLIMIT_NOFILE): "sv, M_SYS_ERR);
    }

    CHECK(v.rlim_cur == *opt, v.rlim_cur, *opt);
    CHECK(v.rlim_max == *opt, v.rlim_max, *opt);
};

void randomize(void* const obuf, std::size_t const obuf_size)
{
    auto const        ptr        = std::bit_cast<std::uint8_t*>(obuf);
    thread_local auto ls_rnd_gen = UniformU8RndGen{};
    std::generate(ptr, ptr + obuf_size, ls_rnd_gen);
}

void daemonize()
{
    auto pid = ::fork();
    if (pid < 0)
    {
        std::exit(EXIT_FAILURE);
    }

    // Let the parent terminate.
    if (pid > 0)
    {
        std::exit(EXIT_SUCCESS);
    }

    // On success: The child process becomes session leader.
    if (::setsid() < 0)
    {
        std::exit(EXIT_FAILURE);
    }

    std::signal(SIGHUP, SIG_IGN);  // NOLINT
    std::signal(SIGPIPE, SIG_IGN); // NOLINT

    // Fork off for the second time.
    pid = ::fork();
    if (pid < 0)
    {
        std::exit(EXIT_FAILURE);
    }

    // Let the parent terminate.
    if (pid > 0)
    {
        std::exit(EXIT_SUCCESS);
    }

    // Set new file permissions.
    ::umask(0);

    // Change the working directory to the root directory or another
    // appropriated directory.
    if (::chdir("/"))
    {
        std::exit(EXIT_FAILURE);
    }

    // Close all open file descriptors.
    for (auto fd = ::sysconf(_SC_OPEN_MAX); fd >= 0; --fd)
    {
        ::close(Number(fd));
    }
}

void setOsThreadTag(char const* const sz_tag)
{
    if (sz_tag && sz_tag[0])
    {
        ::pthread_setname_np(::pthread_self(), sz_tag);
    }
}

std::string_view getOsThreadTag()
{
    thread_local auto tag = std::array<char, 16>{};
    ::pthread_getname_np(::pthread_self(), tag.data(), tag.size());
    return tag.data();
}

std::size_t getOsPid() noexcept
{
    return Number(::getpid());
}

std::size_t getOsTid() noexcept
{
    return Number(::gettid());
}

std::size_t getPageSize() noexcept
{
    static auto const ls = Number(::sysconf(_SC_PAGE_SIZE));
    return ls;
}

std::string getExePathByPid(std::string_view const sv_pid)
{
    auto const fp_proc_pid_exe = "/proc"_fp / sv_pid / "exe"sv;
    if (!exists(fp_proc_pid_exe))
    {
        LOG_ERROR(fp_proc_pid_exe, " does not exist."sv);
        return {};
    }

    auto const p = fs::readSymlink(fp_proc_pid_exe);
    if (gLastError().isFailed() || p.empty())
    {
        LOG_ERROR("fs::readSymlink: "sv, fp_proc_pid_exe, _I_, p);
        return {};
    }

    return p.native();
}

std::string getExePathByPid(size_t const pid)
{
    return getExePathByPid(tos(pid));
}

std::size_t getTotalNumberOfCores() noexcept
{
    static auto const ls = Number(::get_nprocs());
    return ls;
}

std::string getCompileDateTime__( // NOLINTNEXTLINE
    std::string_view const sv_date,
    std::string_view const sv_time)
{
    auto const  parts = split(sv_date, rgx<R"(\s+)">());
    auto        pos   = parts.begin();
    auto        month = tos(*pos++) | actions::transform(::tolower);
    auto const& day   = *pos++;
    auto const& year  = *pos++;

    if ("jan"sv == month)
    {
        month = "01"s;
    }
    else if ("feb"sv == month)
    {
        month = "02"s;
    }
    else if ("mar"sv == month)
    {
        month = "03"s;
    }
    else if ("apr"sv == month)
    {
        month = "04"s;
    }
    else if ("may"sv == month)
    {
        month = "05"s;
    }
    else if ("jun"sv == month)
    {
        month = "06"s;
    }
    else if ("jul"sv == month)
    {
        month = "07"s;
    }
    else if ("aug"sv == month)
    {
        month = "08"s;
    }
    else if ("sep"sv == month)
    {
        month = "09"s;
    }
    else if ("oct"sv == month)
    {
        month = "10"s;
    }
    else if ("nov"sv == month)
    {
        month = "11"s;
    }
    else if ("dec"sv == month)
    {
        month = "12"s;
    }
    else
    {
        RUNTIME_ERROR();
    }

    return scat(year,
                "-"sv,
                month,
                "-"sv,
                align(day, MinWidth(2), FillChar('0')),
                " "sv,
                sv_time);
}

ScopeExitExecutor__::ScopeExitExecutor__(Task fn_task)
    : fn_task_(std::move(fn_task))
{}

ScopeExitExecutor__::~ScopeExitExecutor__()
{
    try
    {
        this->fn_task_();
    }
    catch (...)
    {
        std::abort();
    }
}
} // namespace stx